package com.lsp.microuser.user.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.lsp.microcommon.common.PageParam;
import com.lsp.microcommon.common.PageVo;
import com.lsp.microcommon.constants.PointTypeEum;
import com.lsp.microcommon.constants.RedisKeyEum;
import com.lsp.microcommon.entity.Point;
import com.lsp.microcommon.entity.PointRetry;
import com.lsp.microcommon.entity.PointRule;
import com.lsp.microcommon.entity.User;
import com.lsp.microcommon.exception.CommonException;
import com.lsp.microcommon.utils.*;
import com.lsp.microuser.point.service.PointRetryService;
import com.lsp.microuser.point.service.PointRuleService;
import com.lsp.microuser.point.service.PointService;
import com.lsp.microuser.user.dao.UserDao;
import com.lsp.microuser.user.service.UserService;
import com.lsp.microuser.user.vo.UserVo;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadPoolExecutor;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private MinioUtil minioUtil;

    @Resource
    private UserDao userDao;

    @Resource
    private PointService pointService;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    @Resource
    private PointRuleService pointRuleService;

    @Resource
    private PointRetryService pointRetryService;


    @Override
    public String uploadHeadImg(MultipartFile file) {
        String filename = file.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        filename = uuid + filename.substring(filename.lastIndexOf("."));
        String path = "";
        try {
//            调用工具类将文件上传到oss，返回图片的全路径
            path = OssUtils.uploadFile2OSS(file.getInputStream(), filename);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("上传文件异常");
        }
        User user = new User();
        user.setId(UserUtils.getUser());
        user.setHeadPics(path);
        userDao.update(user);
        return path;
    }

    @Override
    public String uploadHeadImg2(MultipartFile file) {
        String filename = file.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        filename = uuid + filename.substring(filename.lastIndexOf("."));
        String path = "";
        try {
            path = minioUtil.upload(file.getInputStream(), filename, file.getContentType());
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("上传文件异常");
        }
        return path;
    }

    @Override
    public void updatePoint(Integer uid, Integer point) {
        userDao.updatePoint(uid, point);
    }

    @Override
    public User selectById(Integer id) {
        return userDao.selectById(id);
    }

    @Override
    public UserVo myInfo() {
        Integer uid = UserUtils.getUser();
        User user = userDao.selectById(uid);
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user,userVo);
        String yearAndMouth = DateUtils.getYearAndMouth();
        int day = DateUtils.getDay();
        String key = String.format(RedisKeyEum.KEY_SIGN.getKey(),yearAndMouth,uid);

        boolean bit = RedisUtils.getBit(key, day);
        userVo.setSignFlag(bit ? 1 : 0);

        return userVo;
    }

    @Override
    public void updateInfo(User user) {
        userDao.update(user);
    }

    @Override
    public void sign() {
        //  按月签到，
        //  数据存到bitmap
        //  一天签到一次
        //  签到送积分
        //  进行连续签到天数判断，进行额外积分奖励
        Integer uid = UserUtils.getUser();
//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//        String format = sdf.format(new Date());
//        String[] split = format.split("-");
//        String key = String.format("sign:%S:%s",uid,format.substring(0,6));

        LocalDate now = LocalDate.now();
        int year = now.getYear();
        int month = now.getMonthValue();
        int day = now.getDayOfMonth();

        String yearAndMonth = year + "-" + month;
        String key = String.format("sign:%S:%s", yearAndMonth, uid);
//        本例中偏移量为1就是本月一号
        boolean bit = RedisUtils.getBit(key, day);
        if (bit) {
            throw new CommonException("今日已签到");
        }
//        签到
        RedisUtils.setBit(key, day, true);
        List<PointRule> pointRules = pointRuleService.selectPointRuleList(PointTypeEum.TYPE_SIGN.getType());

        if (pointRules == null || pointRules.size() == 0) {
            throw new CommonException("积分规则有误");
        }
        Point point = new Point();
        point.setUid(uid);
        point.setPointType(1);
        point.setPoint(pointRules.get(0).getPoint());
//        如果同步操作数据库送积分异常了，从bitmap中删除签到记录，
//        try {
//            pointService.addPoint(point);
//        }catch (Exception e){
//            e.printStackTrace();
//        删除签到记录
//            throw new CommonException("签到失败");
//        }

//        异步送积分失败，采取补偿机制
//        通过定时器扫描补偿表，将积分数据添加到积分表中
//              如果添加成功，从补偿表中删除数据
//              如果失败，修改重置次数，达到一定次数就不再处理该数据
        threadPoolExecutor.execute(() -> {
                    try {
                        pointService.addPoint(point);
//                        int a = 10/0;
                    } catch (Exception e) {
                        e.printStackTrace();;
//                        补偿机制：将异常的数据存到数据库或者redis，然后通过定时器扫描数据进行扫描处理
//                        将积分写入补偿表中
                        PointRetry pointRetry = new PointRetry();
                        pointRetry.setUid(uid);
                        pointRetry.setPointType(point.getPointType());
                        pointRetry.setPoint(point.getPoint());
                        pointRetryService.addPointRetryInfo(pointRetry);
                    }
                }
        );
//        通过bitfield取签到当天及当天之前数据1001101
        Long v = RedisUtils.bitfield(key, day, 1);
//        方案一：转换为二进制字符串判断
//        extracted(v);
//        方案二：按位与判断连续签到天数
        int count = 0;
//        1110111
        while ((v & 1) == 1) {
            count++;
            v >>= 1;
        }
        List<PointRule> pointRules1 = pointRuleService.selectPointRuleList(PointTypeEum.TYPE_CONTINUE_SIGN.getType());
        if (pointRules1 != null && pointRules1.size() > 0) {
            int continueDays = count;
//            用stream流找相应的积分规则
            pointRules1.stream()
                    .filter((item) -> item.getContinueDays().equals(continueDays))
                    .findFirst()
                    .orElse(null);
        }
        if (pointRules1 != null) {
            Point point2 = new Point();
            point.setUid(uid);
            point.setPointType(PointTypeEum.TYPE_CONTINUE_SIGN.getType());
            point.setPoint(pointRules.get(0).getPoint());
            threadPoolExecutor.execute(() ->
                    pointService.addPoint(point2));
        }

        System.out.println("连续签到：" + count + "天");


//        异步送积分
//        if (count >= 3 && count <= 7) {
//            point.setPoint(15);
//            threadPoolExecutor.execute(() -> pointService.addPoint(point));
//        }else if(count >7 && count <= 20){
//            point.setPoint(20);
//            threadPoolExecutor.execute(() -> pointService.addPoint(point));
//        }else if(count > 20){
//            point.setPoint(25);
//            threadPoolExecutor.execute(() -> pointService.addPoint(point));
//        }

    }

    @Override
    public PageVo<Point> selectPointList(PageParam param) {
        PageHelper.startPage(param.getPageNum(),param.getPageSize());
        List<Point> points = pointService.pointChangeList();
        Page pageVo = (Page) points;

        return new PageVo<>(pageVo.getTotal(),points);
    }

    private void extracted(Long v) {
        char[] s = Long.toBinaryString(v).toCharArray();
        //获得连续签到的天数
        int count = 0;
        for (int i = s.length - 1; i >= 0; i--) {
            if (s[i] == '0') {
                break;
            } else {
                count++;
            }
        }
    }
}
